package com.tarry.socket.group.chat;

import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;
import java.util.Objects;

/**
 * NIO 群聊系统，服务端
 */
@Slf4j
public class GroupChatServer {

    private Selector selector;
    private ServerSocketChannel listenChannel;
    private static final int default_port = 6667;
    private int port;

    public GroupChatServer() {
        this.port = default_port;
    }

    public GroupChatServer(int port){
        this.port = port;
    }

    public void start() {
        try {
            // 得到选择器
            selector = Selector.open();
            // 等到管道
            listenChannel = ServerSocketChannel.open();
            // 绑定端口
            listenChannel.socket().bind(new InetSocketAddress(this.port));
            // 设置非阻塞模式
            listenChannel.configureBlocking(false);
            // 将管道监听到选择器上,并设置对连接事件做出响应
            listenChannel.register(selector, SelectionKey.OP_ACCEPT);
            log.info("{} server started ~~~", listenChannel.getLocalAddress());
            this.listen();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 监听事件
     */
    public void listen() {
        try {
            while (true) {
                // 等待连接2秒
                int count = selector.select(2000);
                if ( count > 0) {// 存在事件处理
                    Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                    while (iterator.hasNext()) {
                        SelectionKey selectionKey = iterator.next();
                        // 判断响应事件类型
                        if ( selectionKey.isAcceptable()) {// 连接事件
                            // 获取连接到管道
                            SocketChannel accept = listenChannel.accept();
                            // 设置为非注释
                            accept.configureBlocking(false);
                            // 将获取到到管道注册到选择器中
                            accept.register(this.selector, SelectionKey.OP_READ);
                            // 打出日志 上线提醒
                            log.info("{} : online ~~~", accept.getRemoteAddress());
                        }else if ( selectionKey.isReadable() ) {// 读取消息事件
                            this.readMsg(selectionKey);
                        }

                        // 移除当前事件 防止重复处理
                        iterator.remove();
                    }

                }
            }

        }catch (Exception e) {
            log.error("listen error:",e);
        }
    }

    /**
     * 读取连接消息
     * @param selectionKey
     */
    private void readMsg(SelectionKey selectionKey){
        SocketChannel channel = null;
        try {
            channel = (SocketChannel) selectionKey.channel();
            ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
            int count = channel.read(byteBuffer);
            byteBuffer.flip();
            if (count == -1) {
                selectionKey.cancel();
                channel.close();
            }else if ( count > 0) {
                String msg = new String(byteBuffer.array(), 0, count, StandardCharsets.UTF_8);
                log.info("from 客户端：{}", msg);
                // 将消息分发到其他客户端
                this.transferMsg(msg, channel);
            }else {
                log.warn("read length is zero !");
            }

        }catch (IOException ioException) {
            try {
                log.warn("{} 离线了～～～", channel.getRemoteAddress());
                // 当前key 取消事件监听
                selectionKey.cancel();
                // 关闭通道～～～
                channel.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 转发消息
     * @param msg 消息内容
     * @param self 当前处理事件通道
     */
    private void transferMsg(String msg, SocketChannel self) {
        log.info("transfer msg ～～～");
        this.selector.keys().forEach((o) -> {
            Channel channel = o.channel();
            // 排除自己
            if (channel instanceof SocketChannel && !Objects.equals(channel,self)) {
                SocketChannel socketChannel = (SocketChannel) channel;
                ByteBuffer byteBuffer = ByteBuffer.wrap(msg.getBytes(StandardCharsets.UTF_8));
                try {
                    byteBuffer.clear();
                    socketChannel.write(byteBuffer);
                    byteBuffer.flip();
                    log.info("to {} msg:{}", socketChannel.getRemoteAddress(), msg);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });
    }

    public static void main(String[] args) {
        new GroupChatServer().start();
    }

}
